%%%-------------------------------------------------------------------
%%% File    : p22.erl
%%% Author  : Plamen Dragozov <plamen at dragozov.com>
%%% Description : 
%%% Using names.txt (right click and 'Save Link/Target As...'), a 
%%% 46K text file containing over five-thousand first names (p22.txt),
%%% begin by sorting it into alphabetical order. Then working out the 
%%% alphabetical value for each name, multiply this value by its 
%%% alphabetical position in the list to obtain a name score.
%%% For example, when the list is sorted into alphabetical order, COLIN, 
%%% which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the 
%%% list. So, COLIN would obtain a score of 938 × 53 = 49714.
%%%
%%% What is the total of all the name scores in the file?
%%% Created :  9 Dec 2008 by  <>
%%%-------------------------------------------------------------------
-module(p22).

-compile(export_all).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: solution(File) -> int()
%% Description: Returns the sum of the scores of the names in the file
%%--------------------------------------------------------------------
solution(File) ->
    sum(load_names(File)).


%%====================================================================
%% Internal functions
%%====================================================================
%Returns a list of the names in the file, sorted in alphabetic order
load_names(File) ->
    {ok, Bin} = file:read_file(File),
    qsort(split(Bin)). 
    
%Sums the scores of the names in the list
sum(Names) ->
    sum(Names, 1, 0).

sum([], _, Acc)->
    Acc;
sum([H|T], I, Acc) ->
    sum(T, I+1, Acc + I*weight(H)).

%Splits the binary into separate names
split(Bin) ->
    split(Bin, 0, 0, []).
split(Bin, Pos, Count, Acc) ->
    case Bin of
        <<_:Pos/binary, $\n>> ->
            Acc;
        <<_:Pos/binary>> ->
            Acc;
        <<_:Pos/binary, $\,,_/binary>> ->
            split(Bin, Pos + 1, 0, Acc);
        <<_:Pos/binary,$\", Name:Count/binary, $\", _/binary>> ->
            split(Bin, Pos + Count + 2, 0, [Name|Acc]);
        <<_:Pos/binary, $\", _:Count/binary, _/binary>> ->
            split(Bin, Pos, Count + 1, Acc)
    end.

a2i(Char) ->%very simplistic, assuming that we'll only get ASCII lower case or capital letters
    Diff = Char - $a + 1,
    case Diff >= 0 of
        true ->
            Diff;
        _ -> Char - $A + 1
    end.

%Calculates the score of a name
weight(Bin) ->
    weight(Bin, 0).

weight(<<>>, Acc) ->
    Acc;

weight(<<Char, Rest/binary>>, Acc) ->
    weight(Rest, a2i(Char) + Acc).

%Compare two names alphabetically.
compare(B1, B2) ->
    case size(B1) >= size(B2) of
        true ->
            compare(B1, B2, 0);
        _ ->
            -compare(B2, B1, 0)
    end.

compare(B1, _, Pos) when Pos >= size(B1) ->
    0;
compare(_, B2, Pos) when Pos >= size(B2) -> 
    1;
compare(B1, B2, Pos) ->
    <<_:Pos/binary, Char1, _/binary>> = B1,
    <<_:Pos/binary, Char2, _/binary>> = B2,
    I1 = a2i(Char1),
    I2 = a2i(Char2),
    case true of
        true when I1 > I2 ->
            1;
        true when I1 < I2 ->
            -1;
        _ -> compare(B1, B2, Pos + 1)
    end.
    
%Simple qsort
qsort([]) -> [];
qsort([H|T]) ->%by the book qsort, not optimal, but should do
    qsort([A || A<-T, compare(A, H) < 0]) ++ [H|qsort([A||A<-T, compare(A, H) >= 0])].
